package com.sk.rpcAssist.consumerAssist;

import com.sk.rpcAssist.annotation.loadBalancer.LoadBalancerRule;
import com.sk.rpcAssist.dataManager.ConsumerDataManager;
import com.sk.rpcAssist.dataManager.DefaultConsumerDataManager;
import com.sk.rpcAssist.domain.ProvideInfoValue;
import com.sk.rpcAssist.io.protocol.MessageProtocolInter;
import com.sk.rpcAssist.io.protocol.ProtocolManage;
import com.sk.rpcAssist.io.protocol.RpcRequest;
import com.sk.rpcAssist.io.protocol.RpcResponse;
import com.sk.rpcAssist.io.socket.ClientSocket;
import com.sk.rpcAssist.policy.ServiceBeanDefinitionRegistry;
import com.sk.rpcAssist.util.ObjectUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Data
@Slf4j
public abstract class AbstractConsumerAstStrategy implements ConsumerAssistStrategyInter {

    //本地缓存zk上provide的信息管理器
    private ConsumerDataManager consumerDataManager;
    //序列化协议管理器
    private ProtocolManage protocolManage;
    //负载均衡策略
    private LoadBalancerRule loadBalancerRule;

    public AbstractConsumerAstStrategy() {
        this.consumerDataManager = loadConsumerDataManager();
        this.protocolManage = ProtocolManage.getInstance();
        this.loadBalancerRule = null;
    }

    //[模板方法]根据请求信息获取serverInfo、序列化策略
    @Override
    public final Object invoke(RpcRequest rpcRequest) {
        //todo：使用负载均衡
        ProvideInfoValue provideInfo = getServerInfoByReq(rpcRequest);
        ObjectUtil.checkNotNull(provideInfo, "provide info is not exist");
        provideInfo.setRpcRequest(rpcRequest);
        String protocolCode = provideInfo.getProtocolCode();
        MessageProtocolInter messageProtocol = null;
        if (!StringUtils.isEmpty(protocolCode)) {
            messageProtocol = protocolManage.getProtocolByCode(protocolCode);
        } else {
            messageProtocol = protocolManage.getDefaultProtocol();
        }
        //todo：抽取接口，兼容多种通讯方式：socket、netty
        Object response = ClientSocket.invoke(provideInfo, messageProtocol);
        if(response==null){
            log.error("invoke rpc method fail");
            return null;
        }
        RpcResponse rpcResponse = (RpcResponse)response;
        if(rpcResponse.getError()){
            throw new RuntimeException("invoke rpc method fail, message is "+ rpcResponse.getErrorMessage());
        }
        return rpcResponse.getResult();
    }

    public ConsumerDataManager loadConsumerDataManager() {
        //判断用户是否提供ConsumerDataManager
        Map<String, ConsumerDataManager> beanMap = ServiceBeanDefinitionRegistry.applicationContext.getBeansOfType(ConsumerDataManager.class);
        ConsumerDataManager current = null;
        if(beanMap.isEmpty()){
            current = new DefaultConsumerDataManager();
        }else if (!beanMap.isEmpty() && beanMap.size() > 1) {
            throw new RuntimeException("Expected only one implementation class for ConsumerDataManager interface, but found multiple,interfaces info :" + ConsumerDataManager.class.getName());
        }else {
            current = (ConsumerDataManager) beanMap.values().toArray()[0];
        }
        return current;
    }


    private Method rpcRequest2Method(RpcRequest rpcRequest) {
        Method method = null;
        try {
            String className = rpcRequest.getClassName();
            Class<?> aClass = Class.forName(className);
            method = aClass.getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return method;
    }

    //todo:考虑使用负载均衡
    protected ProvideInfoValue getServerInfoByReq(RpcRequest rpcRequest) {
        //解决重载方法调用的问题，method名称替换成method名称+""+parameterType数组类型名拼接的参数值
        //不用考虑方法参数的顺序问题，java编译器会帮我们解决
        //key=/interfaceName/consumer/method+parameterTypes类型字符串的hashcode值
//        Class<?>[] parameterTypes = rpcRequest.getParameterTypes();
//        StringBuilder sb = new StringBuilder();
//        for (Class<?> parameterType : parameterTypes) {
//            sb.append(parameterType.getName());
//        }
//        int hashCode = sb.toString().hashCode();
//        String key = "/"+rpcRequest.getClassName()+"/provider/"+rpcRequest.getMethodName()+hashCode;

        ProvideInfoValue provideInfoValue = new ProvideInfoValue(null,null,null,rpcRequest);
        return consumerDataManager.findServerInfo(provideInfoValue);
    }

    public static String processMethodMap(RpcRequest rpcRequest){
        String value = "0";

        Class<?>[] parameterTypes = rpcRequest.getParameterTypes();
        StringBuilder sb = new StringBuilder();
        for (Class<?> parameterType : parameterTypes) {
            sb.append(parameterType.getName());
        }
        return null;
    }

}
